﻿namespace Sina.Weibo
{
    using System;
    using System.Collections.Generic;
    using System.Diagnostics;
    using System.Linq;
    using System.Text;

    internal static partial class WeiboAuthUtility
    {
        private const string ReqStateStringPrefix = AuthConstants.State + "=";
        private static readonly char[] ScopeSeparators = new char[] { ',' };

        /// <summary>
        /// Constructs an authorize url.
        /// </summary>
        public static string BuildAuthorizeUrl(
            string clientId,
            string redirectUrl,
            IEnumerable<string> scopes,
            string state,
            DisplayType display,
            bool forceLogin,
            string language)
        {
            Debug.Assert(!string.IsNullOrEmpty(clientId));
            Debug.Assert(!string.IsNullOrEmpty(redirectUrl));

            IDictionary<string, string> options = new Dictionary<string, string>();
            options[AuthConstants.ClientId] = clientId;
            options[AuthConstants.RedirectUri] = redirectUrl;
            options[AuthConstants.Scope] = BuildScopeString(scopes);
            options[AuthConstants.State] = EncodeAppRequestState(state);
            options[AuthConstants.ResponseType] = ResponseType.Code.ToString().ToLowerInvariant();
            options[AuthConstants.Display] = display.ToString().ToLowerInvariant();
            options[AuthConstants.ForceLogin] = forceLogin.ToString().ToLowerInvariant();
            options[AuthConstants.Language] = EncodeAppRequestLanguage(language);

            return BuildAuthUrl(AuthEndpointsInfo.AuthorizePath, options);
        }

        /// <summary>
        /// Constructs an auth URL.
        /// </summary>
        public static string BuildAuthUrl(string endpointPath, IDictionary<string, string> options)
        {
            Debug.Assert(!string.IsNullOrWhiteSpace(endpointPath));

            StringBuilder urlBuilder = new StringBuilder();
            urlBuilder.Append(AuthEndpointsInfo.AuthEndpoint);

            urlBuilder.Append(endpointPath);

            if (options != null)
            {
                urlBuilder.Append('?');
                urlBuilder.Append(UrlDataEncode(options));
            }

            return urlBuilder.ToString();
        }

        /// <summary>
        /// Constructs an auth token URL.
        /// </summary>
        public static string BuildTokenUrl()
        {
            return BuildAuthUrl(AuthEndpointsInfo.AccessTokenPath, null);
        }

        /// <summary>
        /// Builds exchange code request POST content.
        /// </summary>
        public static string BuildCodeTokenExchangePostContent(
            string clientId, string clientSecret, string redirectUrl, string authorizationCode)
        {
            Debug.Assert(!string.IsNullOrEmpty(clientId));
            Debug.Assert(!string.IsNullOrEmpty(clientSecret));
            Debug.Assert(!string.IsNullOrEmpty(redirectUrl));
            Debug.Assert(!string.IsNullOrEmpty(authorizationCode));

            IDictionary<string, string> options = new Dictionary<string, string>();
            options[AuthConstants.ClientId] = clientId;
            options[AuthConstants.ClientSecret] = clientSecret;
            options[AuthConstants.GrantType] = AuthConstants.AuthorizationCode;
            options[AuthConstants.Code] = authorizationCode;
            options[AuthConstants.RedirectUri] = redirectUrl;

            string postContent = UrlDataEncode(options);

            return postContent;
        }

        /// <summary>
        /// Converts a list of offers into one single offer string with comma as separator.
        /// </summary>
        public static string BuildScopeString(IEnumerable<string> scopes)
        {
            StringBuilder sb = new StringBuilder();
            if (scopes != null)
            {
                bool firstScope = true;
                foreach (string s in scopes)
                {
                    if (firstScope)
                    {
                        firstScope = false;
                    }
                    else
                    {
                        sb.Append(ScopeSeparators[0]);
                    }

                    sb.Append(s);
                }
            }

            return sb.ToString();
        }

        /// <summary>
        /// Parse an request states.
        /// </summary>
        public static IDictionary<string, string> DecodeAppRequestStates(string clientState)
        {
            var states = new Dictionary<string, string>();
            if (!string.IsNullOrEmpty(clientState))
            {
                string[] subStates = clientState.Split('&');
                foreach (string subState in subStates)
                {
                    string[] kv = subState.Split('=');
                    if (kv.Length == 2)
                    {
                        states.Add(kv[0], Uri.UnescapeDataString(kv[1]));
                    }
                }
            }

            return states;
        }

        /// <summary>
        /// Builds an auth state string for a given app state.
        /// </summary>
        public static string EncodeAppRequestState(string state)
        {
            if (state == null)
            {
                return string.Empty;
            }
            else
            {
                return ReqStateStringPrefix + Uri.EscapeDataString(state);
            }
        }

        /// <summary>
        /// Builds a language string for a given app language.
        /// </summary>
        public static string EncodeAppRequestLanguage(string language)
        {
            if (language == null)
            {
                return string.Empty;
            }
            else
            {
                return Uri.EscapeDataString(language);
            }
        }

        /// <summary>
        /// Checks if the first scopes set is a subset of the second scopes set.
        /// </summary>
        public static bool IsSubsetOfScopeRange(IEnumerable<string> scopes1, IEnumerable<string> scopes2)
        {
            Debug.Assert(scopes2 != null);
            bool isSubSet = true;

            if (scopes1 != null)
            {
                foreach (string scope in scopes1)
                {
                    if (!scopes2.Contains<string>(scope.ToLowerInvariant()))
                    {
                        isSubSet = false;
                        break;
                    }
                }
            }

            return isSubSet;
        }

        /// <summary>
        /// Encodes a dictionary into a Url encoded string.
        /// </summary>
        private static string UrlDataEncode(IDictionary<string, string> data)
        {
            StringBuilder dataBuilder = new StringBuilder();
            bool firstParam = true;
            foreach (KeyValuePair<string, string> option in data)
            {
                string format = "&{0}={1}";
                if (firstParam)
                {
                    format = "{0}={1}";
                    firstParam = false;
                }

                dataBuilder.AppendFormat(format, option.Key, Uri.EscapeDataString(option.Value));
            }

            return dataBuilder.ToString();
        }
    }
}
